package com.jplus.core.mvc.servlet;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jplus.core.core.beans.BeanKey;
import com.jplus.core.fault.ServletError;
import com.jplus.core.fault.ServletError.*;
import com.jplus.core.mvc.AnnotationConfigWebApplicationContext;
import com.jplus.core.mvc.RequestMethodAttr;
import com.jplus.core.mvc.WebContext;
import com.jplus.core.mvc.WebUtil;
import com.jplus.core.mvc.anno.ResponseBody;
import com.jplus.core.mvc.reqeust.ContentTypeEnum;
import com.jplus.core.mvc.reqeust.RequestHandle;
import com.jplus.core.mvc.reqeust.RequestMethod;
import com.jplus.core.mvc.response.ResponseHandle;
import com.jplus.core.utill.JUtil;

/**
 * Web Servlet 容器
 * 
 * @author Yuanqy
 *
 */
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = -1009926336723284139L;
	private final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);
	private AnnotationConfigWebApplicationContext appContext;

	public DispatcherServlet() {
		super();
	}

	public DispatcherServlet(AnnotationConfigWebApplicationContext appContext) {
		super();
		this.appContext = appContext;
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		if (appContext == null) {
			this.appContext = new AnnotationConfigWebApplicationContext();
			Enumeration<String> enums = config.getInitParameterNames();
			while (enums.hasMoreElements()) {
				String key = enums.nextElement();
				String val = config.getInitParameter(key);
				appContext.getEnvironment().addUserProp(key, val);
			}
			String contextConfigLocation = appContext.getEnvironment().getProp("contextConfigLocation");
			if (!JUtil.isEmpty(contextConfigLocation)) {
				try {
					appContext.register(Class.forName(contextConfigLocation));
				} catch (ClassNotFoundException e) {
					log.error("Init error:", e);
				}
			}
		}
		appContext.start();
		defaultServletHandler(config);
	}

	@Override
	public void destroy() {
		log.error("DispatcherServlet Shutdown...");
		appContext.close();
		super.destroy();
	}

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		try {
			// Init DataContext
			WebContext.init(req, res);

			// Get URL
			RequestMethod orgRequestMethod = RequestMethod.getMethod(req.getMethod());
			String orgContentType = JUtil.toString(req.getContentType()).split(";")[0].trim();
			String orgReqeustPath = WebUtil.getRequestPath();
			log.debug("[Action] {}:{}", orgRequestMethod.name(), orgReqeustPath);

			// Get Controller
			Entry<String, BeanKey> sbk = buildController(orgReqeustPath, orgRequestMethod);
			if (sbk == null)
				throw new Error404(orgReqeustPath);
			BeanKey bk = sbk.getValue();
			String reqeustPath = sbk.getKey();
			Object action = appContext.getBean(bk);
			RequestMethodAttr pm = appContext.getRmMethod(reqeustPath);
			if (!(pm.requestProduces.equals(ContentTypeEnum.ALL.type) || pm.requestProduces.equals(orgContentType))) {
				String msg = "[Expected value: " + pm.requestProduces + ";Real value: " + orgContentType + "]:";
				throw new Error400(msg + orgReqeustPath);
			}
			if (!(pm.requestMethod == RequestMethod.ALL || pm.requestMethod == orgRequestMethod)) {
				String msg = "[Expected value: " + pm.requestMethod + ";Real value: " + orgRequestMethod + "]:";
				throw new Error400(msg + orgReqeustPath);
			}

			// Handle Request
			Method method = pm.method;
			Object[] params = RequestHandle.getHandle(orgContentType).buildRequstParam(method, this.appContext);

			// Do Method invoker
			Object result = method.invoke(action, params);

			// Handle Response
			ResponseHandle vr = null;
			ResponseBody resBody = pm.responseBody;
			if (resBody == null)
				vr = ResponseHandle.getDefResponseView(); // 使用默认VIEW实现
			else if (resBody.handle().equals(ResponseHandle.class))
				vr = ResponseHandle.getDefResponseBody(); /// 使用默认BODY实现
			else
				vr = appContext.getBean(resBody.handle());// 使用自定义
			vr.handle(orgReqeustPath, result, req, res, appContext);
		} catch (ServletError e) {
			WebUtil.sendError(e.code, e.getMessage());
		} catch (Exception e) {
			log.error("ERROR:", e);
			WebUtil.sendError(500, e.getMessage());
		} finally {
			WebContext.destroy();
		}
	}

	private Entry<String, BeanKey> buildController(String currentRequestPath, RequestMethod currentRequestMethod) {
		currentRequestPath = ("/" + currentRequestPath).replaceAll("//", "/");
		for (Entry<String, BeanKey> bean : appContext.getRmBean().entrySet()) {
			String requestPath = bean.getKey();
			if (requestPath.equals(currentRequestPath))
				return bean;
		}
		for (Entry<String, BeanKey> bean : appContext.getRmBean().entrySet()) {
			String requestPath = bean.getKey();
			Map<String, String> restfulParam = buildRestful(requestPath, currentRequestPath);
			if (restfulParam != null) {
				WebContext.setRestfulParam(restfulParam);
				return bean;
			}
		}
		return null;
	}

	/**
	 * 匹配restful
	 */
	private Map<String, String> buildRestful(String rp, String crp) {
		Map<String, String> map = new LinkedHashMap<String, String>();
		String[] rps = rp.split("\\/");
		String[] crps = crp.split("\\/");
		if (rps.length != crps.length)
			return null;
		for (int i = 0; i < rps.length; i++) {
			if (rps[i].equals(crps[i])) {
				continue;
			} else if (rps[i].startsWith("{") && rps[i].endsWith("}")) {
				map.put(rps[i].substring(1, rps[i].length() - 1), crps[i]);
				continue;
			} else {
				return null;
			}
		}
		return map;
	}

	private void defaultServletHandler(ServletConfig config) {
		String defaultServletName = appContext.getDefaultServletName();
		List<String> defaultServletMapping = appContext.getDefaultServletMapping();
		if (!defaultServletMapping.isEmpty()) {
			ServletRegistration defaultServlet = config.getServletContext().getServletRegistration(defaultServletName);
			if (defaultServlet != null) {
				for (String str : defaultServletMapping) {
					log.info("Servlet[{}] addMapping:{}", defaultServletName, str);
					defaultServlet.addMapping(str);
				}
			}
		}
	}

}
